/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package hhf.flex.ascreator.patterns;

import hhf.flex.annotations.services.CacheServiceId;
import hhf.flex.annotations.services.CanceledService;
import hhf.flex.annotations.services.ChainLoad;
import hhf.flex.annotations.services.LinkedServices;
import hhf.flex.annotations.services.OfflinePolicyEnum;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 *
 * @author utilisateur
 */
public class ASPattern {

	/**
	 * 
	 * @param out
	 * @throws IOException 
	 */
	public static void writeHeader(OutputStreamWriter out, String simpleName) throws IOException {
		out.write("////////////////////////////////////////////////////////////////////////////////\n");
		out.write("//\n");
		out.write("//  HHF SYSTEMS INCORPORATED\n");
		out.write("//  Copyright 2010 HHF Systems Incorporated\n");
		out.write("//  All Rights Reserved.\n");
		out.write("//\n");
		out.write("//  NOTICE: HHF permits you to use, modify, and distribute this file\n");
		out.write("//  in accordance with the terms of the license agreement accompanying it.\n");
		out.write("//  Generated by ASCreator Project : Factory : " + simpleName + ".\n");
		out.write("//\n");
		out.write("////////////////////////////////////////////////////////////////////////////////\n\n");
	}

	/**
	 * Ecrit le corps par défaut de la fonction Send (méthode qui appelle le 
	 * service distant)
	 * @param out
	 * @param arguments
	 * @param cancelFunctionsNames
	 * @param writeRemoveEntitiesFunction
	 * @throws IOException 
	 */
	public static void writeDefaultSendBody(OutputStreamWriter out, String arguments, List<String> cancelFunctionsNames, List<String> chainLoadFunctionsNames, boolean writeRemoveEntitiesFunction, boolean writeAddEntitiesFunction, List<String> argsName, OfflinePolicyEnum offlinePolicy, String className, String pack, String service) throws IOException {
		out.write("\t\t\tif(NetworkMonitor.available) { // si le réseau est disponible on appelle le service distant\n");
		out.write("\t\t\t\taddNetworkHandlers(); // rajout des handlers réseau\n");
		out.write("\t\t\t\tthis.callDatasFromNetwork(" + arguments + "); // appelle du service distant\n");
		out.write("\n\t\t\t\t//appels des fonctions \"cancel\"\n");
		for (String cancelFunctionName : cancelFunctionsNames) {
			out.write("\t\t\t\tcancel_" + cancelFunctionName + "(" + arguments + ");\n");
		}
		for (String chainLoadFunctionName : chainLoadFunctionsNames) {
			out.write("\t\t\t\t" + chainLoadFunctionName + "(this.datas);\n");
		}

		if (writeAddEntitiesFunction) {
			out.write("\n\t\t\t\t//appels des fonctions \"add\"\n");
			out.write("\t\t\t\taddEntities(" + arguments + ");\n");
		}
		if (writeRemoveEntitiesFunction) {
			out.write("\n\t\t\t\t//appels des fonctions \"remove\"\n");
			out.write("\t\t\t\tremoveEntities(" + arguments + ");\n");
		}
		out.write("\t\t\t}");

		if (offlinePolicy != null) {
			switch (offlinePolicy) {
				case ABORT:
					out.write("\n");
					break;
				case DELAY:
					out.write("else{\n");
					out.write("\t\t\t\t//stockage en base du service pour un appel au retour du réseau.\n");
					out.write("\t\t\t\tvar delayedFunction : " + className + "Delayed = new " + className + "Delayed();\n");
					out.write("\t\t\t\tdelayedFunction.init(" + arguments + ");\n");
					out.write("\t\t\t\tvar d:Date = new Date();\n");
					out.write("\t\t\t\tdelayedFunction.transfertDate = d; \n");
					out.write("\t\t\t\tem.persist(delayedFunction);\n");
					out.write("\t\t\t\t\n");
					out.write("\t\t\t\t//si il n'y en a pas déjà pour ce service,\n");
					out.write("\t\t\t\t//on précise dans DelayedClasses qu'il y a des appels\n");
					out.write("\t\t\t\t//en attente pour le service courant.\n");
					out.write("\t\t\t\tvar cb:CriteriaBuilder = em.getCriteriaBuilder();\n");
					out.write("\t\t\t\tvar query:CriteriaQuery = cb.createQuery(DelayedClasses);\n");
					out.write("\t\t\t\tvar root:Root = query.from(DelayedClasses);\n");
					out.write("\t\t\t\tquery.where(cb.equal(root.get(\"DelayedClass\"),\"" + pack + "." + service + "\"));\n");
					out.write("\t\t\t\ttry {\n");
					out.write("\t\t\t\t\tvar res:DelayedClasses = em.createTypedQuery(query).getSingleResult() as DelayedClasses;\n");
					out.write("\t\t\t\t} catch (e:EntityNotFoundException) { \n");
					out.write("\t\t\t\t\tvar delayed:DelayedClasses = new DelayedClasses();\n");
					out.write("\t\t\t\t\tdelayed.DelayedClass = \"" + pack + "." + service + "\";\n");
					out.write("\t\t\t\t\tem.persist(delayed);\n");
					out.write("\t\t\t\t}\n");
					out.write("\t\t\t\t catch (e:NoResultException) { \n");
					out.write("\t\t\t\t\tvar delayed2:DelayedClasses = new DelayedClasses();\n");
					out.write("\t\t\t\t\tdelayed2.DelayedClass = \"" + pack + "." + service + "\";\n");
					out.write("\t\t\t\t\tem.persist(delayed2);\n");
					out.write("\t\t\t\t}\n");
					for (String cancelFunctionName : cancelFunctionsNames) {
						out.write("\t\t\t\tcancel_" + cancelFunctionName + "(" + arguments + ");\n");
					}
					for (String chainLoadFunctionName : chainLoadFunctionsNames) {
						out.write("\t\t\t\t" + chainLoadFunctionName + "(this.datas);\n");
					}

					if (writeAddEntitiesFunction) {
						out.write("\n\t\t\t\t//appels des fonctions \"add\"\n");
						out.write("\t\t\t\taddEntities(" + arguments + ");\n");
					}

					if (writeRemoveEntitiesFunction) {
						out.write("\n\t\t\t\t//appels des fonctions \"remove\"\n");
						out.write("\t\t\t\tremoveEntities(" + arguments + ");\n");
					}
					out.write("\t\t\t}\n");
					break;
				case ALERT:
					//TODO : coder..
					out.write("\n");
					break;
			}
		} else {
			out.write("\n");
		}

	}

	/**
	 * 
	 * @param out
	 * @param arguments
	 * @throws IOException 
	 */
	public static void writeLoadInLocaleBase(OutputStreamWriter out, String arguments) throws IOException {
		out.write("\t\t\tif(NetworkMonitor.available) {\n");
		out.write("\t\t\t\taddNetworkHandlers(); // rajout des handlers réseau\n");
		out.write("\t\t\t\tthis.callDatasFromNetwork(" + arguments + "); // appelle du service distant\n");
		out.write("\t\t\t}\n");
	}

	/**
	 * Ecrit la méthode qui écrit le corps de la fonction isDataOutOfDate.
	 * @param out
	 * @param arguments
	 * @param className
	 * @param retention
	 * @throws IOException 
	 */
	public static void writeIsDatasOutOfDateBody(OutputStreamWriter out, String arguments, String className, long retention) throws IOException {

		out.write("\t\t\tvar now:Date = new Date();\n");
		out.write("\t\t\tvar srvid:String = getCacheServiceId(" + arguments + ");\n");
		out.write("\t\t\tvar cb:CriteriaBuilder=em.getCriteriaBuilder();\n");
		out.write("\t\t\tvar query:CriteriaQuery=cb.createQuery(Date);\n");
		out.write("\t\t\tvar root:Root = query.from(" + className + "CacheService);\n");
		out.write("\t\t\tquery.select(root.get(\"transfertDate\"));\n");
		out.write("\t\t\tvar idcondition:Predicate = cb.equal(root.get(\"id\"), srvid);\n");
		out.write("\t\t\tquery.where(idcondition);\n");
		out.write("\t\t\t\tvar date:Date ;\n");
		out.write("\t\t\ttry {\n");
		out.write("\t\t\t\tdate = em.createTypedQuery(query).getSingleResult() as Date;\n");
		out.write("\t\t\t\tdate.time = date.time + " + retention + ";\n");
		out.write("\t\t\t}catch(e:NoResultException) {\n");
		out.write("\t\t\t\treturn true;\n");
		out.write("\t\t\t}\n");
		out.write("\t\t\tif(date >= now)\n");
		out.write("\t\t\t\treturn false;\n");
		out.write("\t\t\telse\n");
		out.write("\t\t\t\treturn true;\n");
	}

	/**
	 * Ecrit la fonction qui permet de récupérer les données en base locale.
	 * @param out
	 * @param returnType
	 * @param argsTyped
	 * @param arguments
	 * @param className
	 * @throws IOException 
	 */
	public static void writeGetDatasFromDB(OutputStreamWriter out, String returnType, String argsTyped, String arguments, String className) throws IOException {
		if (!returnType.equals("void")) {
			out.write("\t\t/**\n");
			out.write("\t\t * Retourne les données issus de la base de données\n");
			out.write("\t\t */\n");
			out.write("\t\tprotected function getDatasFromDB(" + argsTyped + "):" + returnType + " {\n");
			out.write("\t\t\tCONFIG::flex {\n");
			if (returnType.equals("int")) {
				out.write("\t\t\t\treturn 0;\n");
			} else if (returnType.equals("Boolean")) {
				out.write("\t\t\t\treturn false;\n");
			} else {
				out.write("\t\t\t\treturn null;\n");
			}
			out.write("\t\t\t}\n");
			out.write("\t\t\tCONFIG::air {\n");
			out.write("\t\t\t\tidle=false;\n");
			out.write("\t\t\t\tvar srvid:String = getCacheServiceId(" + arguments + ");\n");
			out.write("\t\t\t\tvar cacheService:" + className + "CacheService;\n");
			out.write("\t\t\t\tcacheService = em.find(" + className + "CacheService, srvid);\n");
			out.write("\t\t\t\tidle=true;\n");
			out.write("\t\t\t\treturn cacheService.datas;\n");
			out.write("\t\t\t}\n");
			out.write("\t\t}\n\n");
		}
	}

	/**
	 * Ecrit la fonction qui permet de persister des données en base locale.
	 * @param out
	 * @param returnType
	 * @param argsTyped
	 * @param arguments
	 * @param className
	 * @throws IOException 
	 */
	public static void writeSetDatasFromDB(OutputStreamWriter out, String returnType, String argsTyped, String arguments, String className) throws IOException {
		if (!returnType.equals("void")) {
			out.write("\t\t/**\n");
			out.write("\t\t * persiste les données en base, les argument permettent de qualifié le résultat\n");
			out.write("\t\t */\n");
			out.write("\t\tprotected function setDatasToDB(datas:" + returnType + ((argsTyped.length() > 0) ? ", " + argsTyped : "") + "):void {\n");
			out.write("\t\t\tCONFIG::air {\n");
			out.write("\t\t\t\tidle=false;\n");
			out.write("\t\t\t\tvar cacheService:" + className + "CacheService = new " + className + "CacheService();\n");
			out.write("\t\t\t\tvar objs:Object = {};\n");
			out.write("\t\t\t\tcacheService.id = getCacheServiceId(" + arguments + ")\n");
			out.write("\t\t\t\tvar d:Date = new Date();\n");
			out.write("\t\t\t\tcacheService.transfertDate = d;\n");
			out.write("\t\t\t\tcacheService.datas = datas;\n");
			out.write("\t\t\t\ttry { \n");
			out.write("\t\t\t\t\tem.persist(cacheService);\n");
			out.write("\t\t\t\t} catch(e:EntityExistsException) {\n");
			out.write("\t\t\t\t\t//on va écraser les données déjà cachées si il y en\n ");
			out.write("\t\t\t\t\t//car elles sont forcément plus anciennes que les courantes.\n ");
			out.write("\t\t\t\t\tvar oldCacheService:" + className + "CacheService;\n");
			out.write("\t\t\t\t\toldCacheService = em.find(" + className + "CacheService, cacheService.id);\n");
			out.write("\t\t\t\t\tem.remove(oldCacheService);\n");
			out.write("\t\t\t\t\tem.persist(cacheService);\n");
			out.write("\t\t\t\t}\n");
			out.write("\t\t\t\tidle=true;\n");
			out.write("\t\t\t}\n");
			out.write("\t\t\tCONFIG::flex {\n");
			out.write("\t\t\t}\n");
			out.write("\t\t}\n\n");
		}
	}

	/**
	 * Ecrit la fonction getCacheServiceId qui retourne la clé de cache
	 * pour le service concerné.
	 * @param out
	 * @param method
	 * @param argNames
	 * @param argsTyped
	 * @throws IOException 
	 */
	public static void writeCacheServiceIdGetter(OutputStreamWriter out, Method method, List<String> argNames, String argsTyped) throws IOException {
		out.write("\t\t/**\n");
		out.write("\t\t * génération de la clé de cache\n");
		out.write("\t\t */\n");
		out.write("\t\tpublic static function getCacheServiceId(" + argsTyped + "):String {\n");

		//Si pas de paramètres à la méthode, on suppose qu'il n'y aura qu'un seul élément dans la base local 
		if (argNames.isEmpty()) {
			out.write("\t\t\treturn \"UNIQUE\";\n");
		} else {
			out.write("\t\t\tvar cacheServiceId:String = \"\";\n");

			Annotation[][] paramAnnotations = method.getParameterAnnotations();
			int i = 0;
			int flag = 0;
			for (Annotation[] annotations : paramAnnotations) {
				for (Annotation annotation : annotations) {
					if (annotation instanceof CacheServiceId) {
						flag = 1;
						CacheServiceId cacheServiceId = (CacheServiceId) annotation;
						out.write("\t\t\tcacheServiceId += \"_" + argNames.get(i) + "_\"+" + argNames.get(i) + "." + cacheServiceId.value() + ";\n");
					}
				}
				i++;
			}
			if (flag == 0) {
				//Cas ou il n'y a pas d'annotation ; j'ai repris l'ancienne méthode de Hash des arguments
				out.write("\t\t\tvar objs:Object = {};\n");
				for (String argName : argNames) {
					out.write("\t\t\tobjs." + argName + " = " + argName + ";\n");
				}
				out.write("\t\t\tcacheServiceId = getHashCode(objs);\n");
			}
			out.write("\t\t\treturn cacheServiceId;\n");
		}


		out.write("\t\t}\n");
		out.write("\t\t\n\n");
	}

	/**
	 * Ecrit le corps de la fonction updateAvailibility.
	 * @param out
	 * @throws IOException 
	 */
	public static void writeUpdateAvailabilityBody(OutputStreamWriter out) throws IOException {
		// Que se passe t'il quand l'état du réseau change
		out.write("\t\t\tCONFIG::air {\n");
		out.write("\t\t\t\tavailable = true;\n");
		out.write("\t\t\t}\n");
		out.write("\t\t\tCONFIG::flex {\n");
		out.write("\t\t\tavailable = NetworkMonitor.available;\n");
		out.write("\t\t\t}\n");
	}

	private static String getTabs(int tabs) {
		String result = "";
		for (int i = 0; i < tabs; i++) {
			result += "\t";
		}
		return result;
	}

	/**
	 * retourne les imports nécessaire à l'implémentation des fonctions liées aux
	 * annotations de la méthode.
	 * exemple : imports des annotations CancelService et ChainLoad... etc
	 * @param method
	 * @return 
	 */
	public static Set<String> getAnnotationImports(Method method) {
		Set<String> imports = new HashSet<String>();

		//imports liés à @LinkedServices
		LinkedServices linkedServices = method.getAnnotation(LinkedServices.class);
		if (linkedServices != null) {

			//imports liés à @CanceledService
			for (CanceledService canceledService : linkedServices.canceledServices()) {
				if (canceledService.methodClass() != null) {
					String className = canceledService.methodClass().getSimpleName();
					imports.add(canceledService.methodClass().getPackage().getName() + "." + className.substring(0, 1).toLowerCase() + className.substring(1) + "." + canceledService.method());
				}
			}

			for (ChainLoad chainLoad : linkedServices.chainLoads()) {
				if (chainLoad.methodClass() != null) {
					String className = chainLoad.methodClass().getSimpleName();
					imports.add(chainLoad.methodClass().getPackage().getName() + "." + className.substring(0, 1).toLowerCase() + className.substring(1) + "." + chainLoad.method());
				}
			}
		}
		return imports;
	}
}
